package com.xyzboom.utils;

import com.xyzboom.database.entity.Entity;
import com.xyzboom.database.entity.EntityColumnEnum;
import com.xyzboom.database.entity.exception.EntityColumnException;
import com.xyzboom.database.entity.exception.NotEntityClassException;
import com.xyzboom.database.utils.SQLLimit;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;

public class Page<T> {
    private Enum<?> entityColumnNull;
    private Class<?> entityColumnClass;
    private Enum<?> orderBy = null;
    private boolean descend = false;

    public Enum<?> getOrderBy() {
        return orderBy;
    }

    public boolean isDescend() {
        return descend;
    }

    public void setOrderBy(String orderBy) {
        try {
            Method fromStringMethod = entityColumnClass.
                    getDeclaredMethod(EntityColumnEnum.FROM_STRING_METHOD_NAME, String.class);
            this.orderBy = (Enum<?>) fromStringMethod.invoke(null, orderBy);
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            this.orderBy = entityColumnNull;
        }
    }

    public void setDescend(String descend) {
        try {
            this.descend = Boolean.parseBoolean(descend);
        } catch (Exception e) {
            this.descend = false;
        }
    }

    @SuppressWarnings("unchecked")
    public static <T> Page<T> cast(Page<?> page) {
        return (Page<T>) page;
    }

    public interface SimplePageDelegate<T1> {
        List<T1> getSimplePage(Enum<?> orderBy, boolean descend, SQLLimit limit);
    }

    public interface TotalSizeDelegate {
        Integer getTotalSize();
    }

    public SimplePageDelegate<T> getSimplePageDelegate() {
        return simplePageDelegate;
    }

    public void setSimplePageDelegate(SimplePageDelegate<T> simplePageDelegate) {
        this.simplePageDelegate = simplePageDelegate;
    }

    private SimplePageDelegate<T> simplePageDelegate;

    public TotalSizeDelegate getTotalSizeDelegate() {
        return totalSizeDelegate;
    }

    public void setTotalSizeDelegate(TotalSizeDelegate totalSizeDelegate) {
        this.totalSizeDelegate = totalSizeDelegate;
    }

    private TotalSizeDelegate totalSizeDelegate;
    /**
     * 显示的页码数量<br/>
     * 例如:<br/>
     * &lt;前一页  1  2  ...  5  6  7  ... 12 13  后一页&gt;<br/>
     * 以上写法共9页，省略号也算2页
     */
    private int showPageNum = 9;

    public Page(SimplePageDelegate<T> simplePageDelegate, TotalSizeDelegate totalSizeDelegate,
                Class<?> entityClass)
            throws NotEntityClassException, EntityColumnException {
        if (entityClass.getAnnotation(Entity.class) == null) {
            throw new NotEntityClassException("Class " + entityClass + " is not an Entity Class");
        }
        Method method;
        try {
            method = entityClass.getDeclaredMethod(Entity.GET_COLUMN_ENUM_METHOD_NAME);
        } catch (NoSuchMethodException e) {
            throw new NotEntityClassException(
                    "Class " + entityClass + " has @Entity but does not have " +
                            Entity.GET_COLUMN_ENUM_METHOD_NAME + " method");
        }
        try {
            Object o = method.invoke(null);
            if (o != null) {
                entityColumnClass = (Class<?>) o;
                if (entityColumnClass.getAnnotation(EntityColumnEnum.class) == null) {
                    throw new EntityColumnException("Not a column class");
                }
                Method getColumnNullMethod;
                getColumnNullMethod = entityColumnClass.
                        getDeclaredMethod(EntityColumnEnum.GET_COLUMN_NULL_METHOD_NAME);
                Object o1 = getColumnNullMethod.invoke(null);
                this.entityColumnNull = (Enum<?>) o1;
            }
        } catch (IllegalAccessException | InvocationTargetException | ClassCastException e) {
            throw new EntityColumnException("Fail to get entity's column class");
        } catch (NoSuchMethodException e) {
            throw new EntityColumnException(
                    "Class " + entityColumnClass + " has @Entity but does not have " +
                            EntityColumnEnum.GET_COLUMN_NULL_METHOD_NAME + " method");
        }
        this.simplePageDelegate = simplePageDelegate;
        this.totalSizeDelegate = totalSizeDelegate;
    }

    public int getShowPageNum() {
        return showPageNum;
    }

    public void setShowPageNum(int showPageNum) {
        this.showPageNum = showPageNum;
    }

    /**
     * 当前页码
     */
    private int pageNo = 1;
    /**
     * 分页大小，即{@link Page#getList}的最大大小
     */
    private int pageSize = 10;

    public int getPageSize() {
        return pageSize;
    }

    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }

    public static class PageSizeOutOfBoundsException extends IndexOutOfBoundsException {
        public PageSizeOutOfBoundsException() {
        }

        public PageSizeOutOfBoundsException(String s) {
            super(s);
        }
    }

    public int getPageNo() {
        return pageNo;
    }

    public void setPageNo(int pageNo) {
        int totalSize = getTotalElementSize();
        int maxPageNo = (int) Math.ceil(totalSize * 1.0 / pageSize);
        if (pageNo > maxPageNo) {
            throw new PageSizeOutOfBoundsException();
        }
        if (pageNo <= 0) {
            throw new PageSizeOutOfBoundsException();
        }
        this.pageNo = pageNo;
    }

    /**
     * 获取当前页面内容
     *
     * @return null 如果发生错误
     */
    public List<T> getList(Enum<?> oderBy, boolean descend) {
        return simplePageDelegate.getSimplePage(oderBy, descend,
                new SQLLimit(pageNo - 1, pageSize));
    }

    public List<T> getList() {
        if (orderBy == null) {
            orderBy = entityColumnNull;
        }
        return getList(orderBy, descend);
    }

    /**
     * 总{@link T}的数量<br/>
     *
     * @return null 如果发生错误
     */
    public Integer getTotalElementSize() {
        return totalSizeDelegate.getTotalSize();
    }

    public int getPrevPage() {
        if (pageNo > 1) {
            return pageNo - 1;
        }
        return -1;
    }

    public int getTotalPageSize() {
        int totalSize = getTotalElementSize();
        return (int) Math.ceil(totalSize * 1.0 / pageSize);
    }

    public int getNextPage() {
        if (pageNo < getTotalPageSize()) {
            return pageNo + 1;
        }
        return -1;
    }
}
